#include "Subsystems/BlockBehaviors/SubsystemSoilBlockBehavior.h"
#include "GameSingleton.h"
#include "GameRandom.hpp"
#include "Subsystems/SubsystemTimeOfDay.h"
#include "Subsystems/SubsystemTerrain.h"
#include "Subsystems/SubsystemGameInfo.h"
#include "Managers/BlocksManager.h"
#include "Log.h"

#include "Components/ComponentBody.h"

#include "Blocks/SoilBlock.h"
#include "Blocks/WaterBlock.h"
#include "Blocks/DirtBlock.h"

namespace PocketSurvival
{
    SubsystemSoilBlockBehavior::SubsystemSoilBlockBehavior()
    {
        m_handleBlocks.push_back(SoilBlock::Index());
    }

    bool SubsystemSoilBlockBehavior::degradesSoilIfOnTopOfIt(int32_t value)
    {
        int32_t num = Terrain::ExtractContents(value);
        Block *block = GameSingleton::blocksManager->blocks[num];
        if (false == block->isFaceTransparent(5, value))
        {
            return block->IsCollidable;
        }
        return false;
    }

    bool SubsystemSoilBlockBehavior::determineHydration(int32_t x, int32_t y, int32_t z, int32_t steps)
    {
        if (steps > 0 && y > 0 && y < 254)
        {
            if (determineHydrationHelper(x - 1, y, z, steps - 1))
            {
                return true;
            }
            if (determineHydrationHelper(x + 1, y, z, steps - 1))
            {
                return true;
            }
            if (determineHydrationHelper(x, y, z - 1, steps - 1))
            {
                return true;
            }
            if (determineHydrationHelper(x, y, z + 1, steps - 1))
            {
                return true;
            }
            if (steps >= 2)
            {
                if (determineHydrationHelper(x, y - 1, z, steps - 2))
                {
                    return true;
                }
                if (determineHydrationHelper(x, y + 1, z, steps - 2))
                {
                    return true;
                }
            }
        }
        return false;
    }

    bool SubsystemSoilBlockBehavior::determineHydrationHelper(int32_t x, int32_t y, int32_t z, int32_t steps)
    {
        int32_t cellValueFast = m_terrain->getCellValue(x, y, z);
        int32_t num = Terrain::ExtractContents(cellValueFast);
        int32_t data = Terrain::ExtractData(cellValueFast);
        switch (num)
        {
        case WaterBlock::Index():
            return true;
        case SoilBlock::Index():
            if (SoilBlock::GetHydration(data))
            {
                return determineHydration(x, y, z, steps);
            }
            break;
        }
        if (num == 2)
        {
            return determineHydration(x, y, z, steps);
        }
        return false;
    }

    void SubsystemSoilBlockBehavior::onCollide(const CellFace &cellFace, float velocity, ComponentBody *componentBody)
    {
        if (componentBody->mass > 20.0f && componentBody->isSneaking != true)
        {
            Vector3 velocity2;
            componentBody->getVelocity(velocity2);

            if (velocity2.posY < -0.5f || (velocity2.posY < 0.0f && GameSingleton::gameRandom->getBool(0.5f) && velocity2.lengthSquared() > 1.0f))
            {
                m_toDegradeLock.lock();
                m_toDegrade.insert(cellFace.point);
                m_toDegradeLock.unlock();
            }
        }
    }

    void SubsystemSoilBlockBehavior::onPoll(int32_t value, int32_t x, int32_t y, int32_t z)
    {
        if (m_gameInfo->environmentBehaviorMode == EnvironmentBehaviorMode::Static)
        {
            return;
        }
        bool hydration = SoilBlock::GetHydration(Terrain::ExtractData(value));
        if (determineHydration(x, y, z, 3))
        {
            if (!hydration)
            {
                m_toHydrateLock.lock();
                m_toHydrate[Point3(x, y, z)] = true;
                m_toHydrateLock.unlock();
            }
        }
        else if (hydration)
        {
            m_toHydrateLock.lock();
            m_toHydrate[Point3(x, y, z)] = false;
            m_toHydrateLock.unlock();
        }
    }

    void SubsystemSoilBlockBehavior::onNeighborBlockChanged(int32_t x, int32_t y, int32_t z, int32_t neighborX, int32_t neighborY, int32_t neighborZ)
    {
        int32_t cellValue = m_terrain->getCellValue(x, y + 1, z);
        if (degradesSoilIfOnTopOfIt(cellValue))
        {
            int32_t cellValue2 = m_terrain->getCellValue(x, y, z);
            m_terrain->changeCell(x, y, z, Terrain::ReplaceContents(cellValue2, DirtBlock::Index()));
        }
    }

    void SubsystemSoilBlockBehavior::update(float dt)
    {
        if (m_timeOfDay->periodicGameTimeEvent(2.5, 0.0) == true)
        {
            m_toDegradeLock.lock();
            for (const Point3 &key2 : m_toDegrade)
            {
                int32_t cellValue = m_terrain->getCellValue(key2);
                if (Terrain::ExtractContents(cellValue) == SoilBlock::Index())
                {
                    m_terrain->changeOldCell(key2, cellValue, Terrain::ReplaceContents(cellValue, 2));
                }
            }
            m_toDegrade.clear();
            m_toDegradeLock.unlock();
        }
        if (m_timeOfDay->periodicGameTimeEvent(10.0, 0.0) == true)
        {
            m_toHydrateLock.lock();
            for (const auto &pair : m_toHydrate)
            {
                int32_t cellValue2 = m_terrain->getCellValue(pair.first);
                if (Terrain::ExtractContents(cellValue2) == SoilBlock::Index())
                {
                    int32_t data = SoilBlock::SetHydration(Terrain::ExtractData(cellValue2), pair.second);
                    int32_t value2 = Terrain::ReplaceData(cellValue2, data);
                    m_terrain->changeOldCell(pair.first, cellValue2, value2);
                }
            }
            m_toHydrate.clear();
            m_toHydrateLock.unlock();
        }
    }

    void SubsystemSoilBlockBehavior::load(const nlohmann::json &json)
    {
        m_timeOfDay = GameSingleton::singletonPtr->getSingleton<SubsystemTimeOfDay>();
        m_terrain = GameSingleton::singletonPtr->getSingleton<SubsystemTerrain>();
        m_gameInfo = GameSingleton::singletonPtr->getSingleton<SubsystemGameInfo>();
    }

    static const std::string subsystemName = "SoilBlockBehavior";
    const std::string &SubsystemSoilBlockBehavior::getName() const
    {
        return subsystemName;
    }
    SubUpdateType SubsystemSoilBlockBehavior::getUpdateType()
    {
        return SubUpdateType::MultiThreadUpdate;
    }
}
